package debtbundler.domain.bll.solver;

import java.util.Collection;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;

import debtbundler.domain.entity.Debt;
import debtbundler.domain.entity.Person;
import debtbundler.domain.entity.impl.EntityManager;


public class SimpleSolver extends SolverSkeleton {
	private static Logger logger = Logger.getLogger(SimpleSolver.class.getName());
	
	private Queue<Person> debtors = new PriorityQueue<Person>(20, new ComparatorImpl(ComparatorImpl.SORT_ASCENDING));
	private Queue<Person> creditors = new PriorityQueue<Person>(20, new ComparatorImpl(ComparatorImpl.SORT_DESCENDING));
	
	/**
	 * Solves the debts between given persons.

	 * @param persons	persons whose debts to calculate
	 * @throws UnbalancedCostsException
	 * 	if the costs and incomes to the given persons are not balanced within accuracy of 0.005
	 */	
	public SimpleSolver(Collection<Person> persons) throws UnbalancedCostsException {
		solve(persons);
	}	

	@Override
	protected void calculateSolution() {
		this.debtors.addAll(super.debtors);
		this.creditors.addAll(super.creditors);
		
		while (!(debtors.isEmpty() || creditors.isEmpty())) {
			logger.log(Level.FINER, "#Debtors: {0}, #creditors {1}", new Object[]{debtors.size(), creditors.size()});
			
			Person debtor = debtors.poll(); 
			double totalToPay = Math.abs(debtor.getBalance());
			Person creditor = creditors.poll();
			double totalToGet = Math.abs(creditor.getBalance());
			
			logger.log(Level.FINER, "Matching debtor {0} ({1}) and creditor {2} ({3})", new Object[]{debtor, debtor.getBalance(), debtor, creditor.getBalance()});
			
			Debt debt = EntityManager.createDebt(debtor, creditor);
			if (totalToGet > totalToPay) {
				debt.setValue(totalToPay);
				creditors.add(creditor);
			} else if (totalToGet < totalToPay) {
				debt.setValue(totalToGet);
				debtors.add(debtor);
			} else {
				debt.setValue(totalToGet);
			}
			
			debts.add(debt);
			logger.log(Level.FINER, "Created debt {0}", debt);
		}
		
		logger.log(Level.FINER, "Debts solved");
	}
	
	
	/** 
	 * This comparator sorts persons in ascending or descending order 
	 * by their absolute balance values. 
	 * 
	 * @author Anssi
	 *
	 */
	private class ComparatorImpl implements Comparator<Person> {
		
		public static final int SORT_ASCENDING = 1;
		public static final int SORT_DESCENDING = -1;
		
		int sortDir;
		
		public ComparatorImpl(int sortDir) {
			this.sortDir = sortDir;
		}

		@Override
		public int compare(Person p1, Person p2) {
			Double b1 = Math.abs(p1.getBalance());
			Double b2 = Math.abs(p2.getBalance());
			return (int) (Math.signum(sortDir)*b1.compareTo(b2));
		}
		
	}
	
}
